{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 获得前n个主成分 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "X = np.empty((100, 2))\n",
    "X[:,0] = np.random.uniform(0., 100., size=100)\n",
    "X[:,1] = 0.75 * X[:,0] + 3. + np.random.normal(0, 10., size=100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def demean(X):\n",
    "    return X - np.mean(X, axis=0)\n",
    "X = demean(X)                      #画图省略  与前一节一样"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(w, X):\n",
    "    return np.sum((X.dot(w)**2))/len(X)\n",
    "def df(w, X):                     # 使用的是df_math\n",
    "    return X.T.dot(X.dot(w)) * 2./len(X)\n",
    "def direction(w):\n",
    "    return w/np.linalg.norm(w)\n",
    "\n",
    "def first_component(X, initial_w, eta, n_iters = 1e4, epsilon=1e-8):  #求给定X样本第一主成分对应w\n",
    "    w = direction(initial_w) \n",
    "    cur_iter = 0\n",
    "    while cur_iter < n_iters:\n",
    "        gradient = df(w, X)\n",
    "        last_w = w\n",
    "        w = w + eta * gradient\n",
    "        w = direction(w) \n",
    "        if(abs(f(w, X) - f(last_w, X)) < epsilon):\n",
    "            break\n",
    "        cur_iter += 1\n",
    "    return w"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.79135366, 0.61135864])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "initial_w = np.random.random(X.shape[1])\n",
    "eta = 0.01\n",
    "w = first_component(X, initial_w, eta)\n",
    "w                                              #第一主成分"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "X2 = np.empty(X.shape)                 #去除第一主成分\n",
    "for i in range(len(X)):\n",
    "    X2[i] = X[i] - X[i].dot(w)*w\n",
    "    \n",
    "#等价  X2 = X - X.dot(w).reshape(-1, 1) * w"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.collections.PathCollection at 0x4460fbed08>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.scatter(X2[:,0], X2[:,1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-0.61135618,  0.79135556])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "w2 = first_component(X2, initial_w, eta)\n",
    "w2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3.1052294829359006e-06"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "w.dot(w2)      #验证垂直  w垂直于w2  点乘为0  结果无限趋近于0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "#封装\n",
    "\n",
    "def first_n_components(n, X, eta=0.01, n_iters = 1e4, epsilon=1e-8):\n",
    "    X_pca = X.copy()\n",
    "    X_pca = demean(X_pca)\n",
    "    res = []\n",
    "    for i in range(n):\n",
    "        initial_w = np.random.random(X_pca.shape[1])\n",
    "        w = first_component(X_pca, initial_w, eta)\n",
    "        res.append(w)        \n",
    "        X_pca = X_pca - X_pca.dot(w).reshape(-1, 1)*w\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[array([0.79135364, 0.61135867]), array([-0.61135588,  0.79135579])]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "first_n_components(2, X)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
